#!/usr/bin/env python3
# -*- coding:utf-8 -*-

def str2int(string:str, base=0):
    string = string.lower()
    if base:
        try:
            return int(string, base)
        except ValueError as e:
            raise e

    if string.startswith('0x'):
        return int(string, 16)

    # try 10 first
    try:
        return int(string, 10)
    except ValueError:
        pass

    try:
        return int(string, 16)
    except ValueError as e:
        raise e

class ParamDesc(object):
    def __init__(self, name, defvalue, version=None):
        self._name = name
        self.defvalue = defvalue
        self._version = version

    @property
    def name(self):
        return self._name

    @property
    def version(self):
        return self._version


class ParamConfig(dict):
    def __init__(self, fileversion=None, vercmpfunc=lambda x, y: x - y,
                 *args, **kwargs):
        super().__init__(*args, **kwargs)
        # version from config file
        self._fileversion = fileversion
        self._vercmpfunc = vercmpfunc
        self._config_desc = {}

    @property
    def version(self):
        return self._fileversion

    def _do_get(self, key, default=None):
        if key in self:
            return super().__getitem__(key)

        if key in self._config_desc:
            desc = self._config_desc[key]
            # return default value if fileversion < desc.version
            if desc.version and self._vercmpfunc(self._fileversion, desc.version) < 0:
                return desc.defvalue
        return default

    def __getitem__(self, key):
        val = self._do_get(key)
        if val is None:
            raise KeyError(f"{key} not found")
        return val

    def get(self, key, default=None):
        return self._do_get(key, default)

    def add_desc(self, desc:ParamDesc):
        self._config_desc[desc.name] = desc

    def add_descs(self, descs:list|tuple):
        for desc in descs:
            self.add_desc(desc)

    def get_type(self, typename, key, default):
        if not isinstance(default, typename):
            raise TypeError("Default value must be a boolean")
        val = self.get(key, None)
        return default if val is None else typename(val)

    def get_str(self, key, default=""):
        return self.get_type(str, key, default)

    def get_hexstr(self, key, default="", prefix="0x", case="upper"):
        # FIXME
        return self.get_type(str, key, default).replace(prefix, "", 1).strip().\
               upper() if case == 'upper' else \
               self.get_type(str, key, default).replace(prefix, "", 1).strip().\
               lower() if case == 'lower' else \
               self.get_type(str, key, default) if default else default

    def get_int(self, key, default=0):
        val = self.get(key, default)
        if val is None:
            return default
        if isinstance(val, str):
            return str2int(val)
        return val

    def get_float(self, key, default=0.0):
        return self.get_type(float, key, default)

    def get_bool(self, key, default=False):
        if not isinstance(default, bool):
            raise TypeError("Default value must be a boolean")

        val = self.get(key, default)
        if isinstance(val, str):
            if val.lower() in ("true", "yes", "y", "1"):
                return True
            elif val.lower() in ("false", "no", "n", "0"):
                return False
        if isinstance(val, int):
            return bool(val)
        return bool(default)
